﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Numerics;
using System.Drawing;
using System.Diagnostics;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Threading;
using ArgusLib.Collections;
using ArgusLib.Math;

namespace ArgusLib.Fractals.OrbitFractals
{
	public class DataSetBuddhabrot : DataSet<Complex>, IDataSetPointDistribution
	{
		int[,] pixelCount;
		Size size;
		List<Complex>[,] trace;
		object[,] locks;

		public IndexedPropertyGet<int, int, int> PixelCount { get; private set; }
		public float Average { get; private set; }
		public float Variance { get; private set; }
		public Int32Interval IntervalPixelCount { get; private set; }

		public DataSetBuddhabrot()
			: base()
		{
		}

		public override void Initialize(Parameter parameter)
		{
			base.Initialize(parameter);
			this.pixelCount = new int[parameter.PixelWidth, parameter.PixelHeight];
			this.PixelCount = new IndexedPropertyGet<int, int, int>(
				new IndexedPropertyGet<int, int, int>.GetHandler((x, y) => { return this.pixelCount[x, y]; }));
			this.size = new Size(parameter.PixelWidth, parameter.PixelHeight);
			this.trace = new List<Complex>[parameter.PixelWidth, parameter.PixelHeight];
			this.locks = new object[parameter.PixelWidth, parameter.PixelHeight];
			for (int x = 0; x < this.size.Width; x++)
			{
				for (int y = 0; y < this.size.Height; y++)
				{
					this.locks[x, y] = new object();
					this.trace[x, y] = new List<Complex>();
				}
			}
			//this.seriPos = new List<long>();
			//this.dataCount = new Threading.SynchronizedInt32(0);
			//this.isDumping = false;
			//this.dumpingLock = new object();
			//this.waintHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
		}

		private Point Convert(Complex c)
		{
			c = (c - this.CalculationParameter.ShownArea.Center) / Complex.FromPolarCoordinates(1, -this.CalculationParameter.ShownArea.Rotation);
			double x = c.Real * this.CalculationParameter.PixelWidth / this.CalculationParameter.ShownArea.Width;
			double y = c.Imaginary * this.CalculationParameter.PixelHeight / this.CalculationParameter.ShownArea.Height;
			x = x + this.CalculationParameter.PixelWidth / 2.0;
			y = this.CalculationParameter.PixelHeight / 2.0 - y;
			return new Point((int)x, (int)y);
		}

		protected override void OnZValueChanged(int x, int y, Complex oldValue, Complex newValue)
		{
			base.OnZValueChanged(x, y, oldValue, newValue);
			
			if (Monitor.TryEnter(this.locks[x,y]) == true)
			{
				if (this.trace[x, y] == null)
					return;
				List<Complex> list = this.trace[x, y];
				list.Add(newValue);
			}
		}

		public override void PostProcess()
		{
			long count = 0;
			long points = 0;
			int min = int.MaxValue;
			int max = 0;
			for (int x = 0; x < this.size.Width; x++)
			{
				for (int y = 0; y < this.size.Height; y++)
				{
					if (this.pixelCount[x, y] < min)
						min = this.pixelCount[x, y];
					if (this.pixelCount[x, y] > max)
						max = this.pixelCount[x, y];

					if (this.pixelCount[x, y] > 0)
					{
						count += this.pixelCount[x, y];
						points++;
					}
				}
			}
			this.IntervalPixelCount = new Int32Interval(min, max);
			this.Average = (float)(count / (double)points);

			double vari = 0;
			for (int x = 0; x < this.size.Width; x++)
			{
				for (int y = 0; y < this.size.Height; y++)
				{
					if (this.pixelCount[x, y] < 1)
						continue;

					vari += System.Math.Pow(this.pixelCount[x, y] - this.Average, 2);
				}
			}
			this.Variance = (float)(vari / count);

			base.PostProcess();
		}

		protected override void set_IsFinished(int x, int y, bool value)
		{
			if (Monitor.TryEnter(this.locks[x, y]) == true)
			{
				if (this.trace[x, y] == null)
					return;
				if (this.Iterations[x, y] < this.CalculationParameter.Iterations)
				{
					foreach (Complex c in this.trace[x, y])
					{
						Point p = this.Convert(c);
						if (this.size.Contains(p) == false)
							continue;

						this.pixelCount[p.X, p.Y]++;
					}
				}
				this.trace[x, y].Clear();
				this.trace[x, y] = null;
			}
			base.set_IsFinished(x, y, value);
		}

		public override float GetMemoryUsage(Parameter parameter)
		{
			float baseVal = base.GetMemoryUsage(parameter);
			double bytes = (double)(4 + 2 * IntPtr.Size) * parameter.PixelWidth * parameter.PixelHeight;
			int threads = parameter.ThreadCount;
			if (threads < 1)
				threads = System.Environment.ProcessorCount;
			bytes += 2*threads * parameter.Iterations * 16;

			return baseVal + (float)(bytes / (1 << 20));
		}

		public override float GetMemoryUsage()
		{
			int threads = this.CalculationParameter.ThreadCount;
			if (threads < 1)
				threads = System.Environment.ProcessorCount;

			float val = (2 * threads * this.CalculationParameter.Iterations * 16) / (1 << 20);
			return base.GetMemoryUsage() + val;
		}
	}

	public class DataSetBuddhabrotProvider : IDataSetProvider
	{
		public IDataSet GetDataSet()
		{
			return new DataSetBuddhabrot();
		}

		public Type DataSetType { get { return typeof(DataSetBuddhabrot); } }
	}
}
